
import java.util.*;

/**
 * <p>
 * Define um Elevador que atende numAndares andares enumerados a partir de
 * 1.
 * </p>
 */
class Elevador extends Thread {

  ///////////////////////////////////////
  // attributes


/**
 * <p>
 * Representa direcao em que se esta' movimentando o Elevador.
 * </p>
 */
    private int direcao; 

/**
 * <p>
 * Representa o andar em que se encontra o Elevador.
 * </p>
 */
    private int andarCorrente; 

/**
 * <p>
 * Representa o numero de andares do predio.
 * </p>
 */
    private int numAndares; 

/**
 * <p>
 * Representa o andar para o qual  o Elevador esta' se dirigindo.
 * </p>
 */
    private int proximoDestino; 

/**
 * <p>
 * Representa o identificador do Elevador (enumerados a partir de 1).
 * </p>
 */
    private int indElevador; 

   ///////////////////////////////////////
   // associations

/**
 * <p>
 * Porta integrada ao Elevador.
 * </p>
 */
    protected Porta porta; 
/**
 * <p>
 * Representa uma colecao de instancias de BotaoDeSelecao instalada na
 * parte interna do Elevador. Cada BotaoDeSelecao representa um andar.
 * </p>
 */
    protected ArrayList<BotaoDeSelecao> botaoDeSelecao = new ArrayList<BotaoDeSelecao>();
/**
 * <p>
 * Motor integrado ao Elevador.
 * </p>
 */
    protected Motor motor; 
/**
 * <p>
 * Controlador ao qual esta' subordinado o Elevador.
 * </p>
 */
    protected Controlador controlador; 
  
/**
 * <p>
 * Representa uma instancia de BotaoDeEmergencia instalada na
 * parte interna do Elevador.
 * </p>
 */
    protected BotaoDeEmergencia botaoDeEmergencia;
/**

  ///////////////////////////////////////
  // operations


/**
 * <p>
 * Construtor de uma instancia de Elevador.
 * </p>
 * 
 * @param controlador 
 * @param indElevador <p>
 * Indicador do Elevador (enumerados a partir de 1);
 * </p>
 * 
 * @param numAndares - numero de andares do predio que contem o Elevador.
 * 
 * @param posicaoInicial 
 * @param portaFechada 
 * @return um Elevador para um predio numAndares andares.
 */
    public  Elevador(Controlador controlador, int indElevador, int numAndares, int posicaoInicial, boolean portaFechada) {        
        int andar;
        this.controlador = controlador;
        this.indElevador = indElevador;
        this.numAndares = numAndares;
        
        andarCorrente = posicaoInicial;
        direcao = Controlador.parado;
        andarCorrente = 1;
        
        porta = new Porta(portaFechada,this);
        porta.start();
        
        motor = new Motor(direcao,this);
        motor.start();
        
        botaoDeSelecao.ensureCapacity(numAndares + 1);
        botaoDeSelecao.add(0, null);
        for (andar = 1; andar <= numAndares; andar++)
            botaoDeSelecao.add(andar, new BotaoDeSelecao(andar, this));
        
        botaoDeEmergencia = new BotaoDeEmergencia(this);
    } // end Elevador        

/**
 * <p>
 * Solicita a vinda do Elevador para andarDesejado e indica a intecao de
 * direcao do passageiro.
 * </p>
 * 
 * @param direcaoDesejada - direcao em que o passageiro pretende se
 * deslocar.
 * 
 * @param andarDesejado - andar em que se encontra o passageiro que
 * solicitou o Elevador.
 */
    public synchronized void moverPara(int andarDesejado) {        
        if (porta.isFechada()) {
            if (!motor.isLigado()) {
                if (andarDesejado > andarCorrente) 
                	direcao = Controlador.paraCima;
                else if (andarDesejado < andarCorrente) 
                	direcao = Controlador.paraBaixo;
                else 
                	direcao = Controlador.parado;
                if (direcao == Controlador.parado) {
                    notificarBotoesChegadaElevador();
                    porta.abrir();
                } else {
                    proximoDestino = andarDesejado;
                    motor.ligar(direcao);
                }
            }
        } else {
            notificarBotoesChegadaElevador();
            porta.reabrir();
        }
    } // end moverPara        

/**
 * <p>
 * Reage 'a notificacao de que o Elevador parou.
 * </p>
 */
    public synchronized void registrarMotorDesligado() {        
        registrarNovaPosicao();
        proximoDestino = determinarProximoDestino();
        if (andarCorrente == proximoDestino) {
            notificarBotoesChegadaElevador();
            porta.abrir();
        } else 
        	motor.ligar(direcao);
    } // end registrarMotorDesligado        

/**
 * <p>
 * Reage 'a notificacao de que a Porta se fechou.
 * </p>
 */
    public synchronized void registrarPortaFechada() {        
        proximoDestino = determinarProximoDestino();
        if (proximoDestino == andarCorrente) 
        	registrarFinalDirecao();
        else 
        	motor.ligar(direcao);
    } // end registrarPortaFechada        

/**
 * <p>
 * Determina o proximo andar a parar na direcao do movimento do Elevador.
 * </p>
 * 
 * @return o numero do andar da proxima parada na direcao em que se
 * movimenta o Elevador. Se o andar mais proximo determinado for igual ao
 * andar corrente, entao nao ha' mais requisicao pendente na direcao em que
 * se movimenta o Elevador.
 */
    private int determinarProximoDestino() {        
        int prox,andar;
        boolean naoAchou;
        if (direcao == Controlador.paraCima) {
            prox = numAndares;
            andar = andarCorrente;
            naoAchou = true;
            do {
                if (controlador.isArmadoBotaoSubida(andar)) {
                    naoAchou = false;
                    prox = andar;
                } else andar++;
            } while (naoAchou && andar < prox);
            if (prox == numAndares) prox = numAndares + 1;
            andar = andarCorrente;
            naoAchou = true;
            do {
                if (getBotaoDeSelecao(andar).isArmado()) {
                    prox = andar;
                    naoAchou = false;
                } else andar++;
            } while (naoAchou && andar < prox);
            if (prox == numAndares + 1) {
                andar = numAndares;
                naoAchou = true;
                do {
                    if (controlador.isArmadoBotaoDescida(andar)) {
                        naoAchou = false;
                        prox = andar;
                    } else andar--;
                } while (naoAchou && andar >= andarCorrente);
            }
            if (prox == numAndares + 1) prox = andarCorrente;
        } else {
            prox = 1;
            andar = andarCorrente;
            naoAchou = true;
            do {
                if (controlador.isArmadoBotaoDescida(andar)) {
                    naoAchou = false;
                    prox = andar;
                } else andar--;
            } while (naoAchou && andar > prox);
            if (prox == 1) prox = 0;
            naoAchou = true;
            andar = andarCorrente;
            do {
                if (getBotaoDeSelecao(andar).isArmado()) {
                    naoAchou = false;
                    prox = andar;
                } else andar--;
            } while (naoAchou && andar > prox);
            if (prox == 0) {
                andar = 1;
                naoAchou = true;
                do {
                    if (controlador.isArmadoBotaoSubida(andar)) {
                        prox = andar;
                        naoAchou = false;
                    } else andar++;
                } while (naoAchou && andar <= andarCorrente);
            }
            if (prox == 0) 
            	prox = andarCorrente;
        }
        return prox;
    } // end determinarProximoDestino        

/**
 * <p>
 * Atualiza o estado do Elevador referente ao andar corrente. O elevador se
 * encontra um andar acima ou abaixo da posicao corrente anterior.
 * </p>
 */
    private void registrarNovaPosicao() {        
        if (direcao == Controlador.paraCima) 
        	andarCorrente++;
        else if (direcao == Controlador.paraBaixo) 
        	andarCorrente--;
        controlador.registrarPosicaoElevador(indElevador, andarCorrente);
    } // end registrarNovaPosicao        

/**
 * <p>
 * Registra o fim do movimento em uma direcao e toma as acoes cabiveis
 * (inverte a direcao e aciona o Motor caso haja um destino na direcao
 * inversa).
 * </p>
 */
    public synchronized void registrarFinalDirecao() {        
        inverterDirecao();
        proximoDestino = determinarProximoDestino();
        if (proximoDestino == andarCorrente) 
        	direcao = Controlador.parado;
        else 
        	motor.ligar(direcao);
    } // end registrarFinalDirecao        

/**
 * <p>
 * Inverte a direcao do Elevador.
 * </p>
 */
    private void inverterDirecao() {        
        if (direcao == Controlador.paraCima) 
        	direcao = Controlador.paraBaixo;
        else if (direcao == Controlador.paraBaixo) 
        	direcao = Controlador.paraCima;
    } // end inverterDirecao        

/**
 * <p>
 * Retorna BotaoDeSelecao associado a andar.
 * </p>
 * 
 * @return um BotaoDeSelecao.
 * 
 * @param andar - andar representado pelo BotaoDeSelecao desejado.
 */
    private BotaoDeSelecao getBotaoDeSelecao(int andar) {        
        return botaoDeSelecao.get(andar);
    } // end getBotaoDeSelecao        

/**
 * <p>
 * Inicia a execucao concorrente de Controlador.
 * </p>
 */
    public synchronized void notificarBotoesChegadaElevador() {        
        int prox;
        getBotaoDeSelecao(andarCorrente).registrarElevadorNoAndar();
        prox = determinarProximoDestino();
        if (andarCorrente == prox) {
            controlador.registrarChegadaElevadorBotaoSubida(andarCorrente);
            controlador.registrarChegadaElevadorBotaoDescida(andarCorrente);
        } else if (direcao == Controlador.paraCima)
            controlador.registrarChegadaElevadorBotaoSubida(andarCorrente);
        else 
        	controlador.registrarChegadaElevadorBotaoDescida(andarCorrente);
    } // end notificarBotoesChegadaElevador        

/**
 * <p>
 * Repassa a informacao de que o seu BotaoDeSelecao representante de
 * andarAssociado foi armado.
 * </p>
 * 
 * @param andarAssociado - andar associado ao BotaoDeSelecao.
 */
    public synchronized void registrarBotaoDeSelecaoArmado(int andarAssociado) {        
        controlador.registrarBotaoDeSelecaoArmado(indElevador, andarAssociado);
    } // end registrarBotaoDeSelecaoArmado        

/**
 * <p>
 * Recebe e repassa a informacao de que o Botao do Elevador representante
 * do andarAcionado foi desarmado.
 * </p>
 * 
 * @param andarAssociado - andar associado ao Botao.
 */
    public synchronized void registrarBotaoDeSelecaoDesarmado(int andarAssociado) {        
        controlador.registrarBotaoDeSelecaoDesarmado(indElevador, andarAssociado);
    } // end registrarBotaoDeSelecaoDesarmado        

/**
 * <p>
 * Repassa a informacao de que o estado da Porta do Elevador se alterou.
 * </p>
 * 
 * @param fechada - true se a Porta encontra-se agora fechada.
 */
    public synchronized void notificarEstadoPorta(boolean fechada) {        
        controlador.notificarEstadoPorta(indElevador, fechada);
    } // end notificarEstadoPorta        

/**
 * <p>
 * Informa ao Elevador que o seu BotaoDeSelecao associado a andar foi
 * acionado pelo usuario.
 * </p>
 * 
 * @param andar - andar associado ao BotaoDeSelecao acionado.
 */
    public void registrarBotaoDeSelecaoAcionado(int andar) {        
        getBotaoDeSelecao(andar).solicitarAndar();
    } // end registrarBotaoDeSelecaoAcionado        

/**
 * <p>
 * Verifica se a Porta encontra-se fechada.
 * </p>
 * 
 * @return true se Porta fechada.
 */
    public boolean isPortaFechada() {        
        return porta.isFechada();
    } // end isPortaFechada        

/**
 * <p>
 * Inicia a execucao concorrente de Elevador.
 * </p>
 */
    public void run() {        
        
    } // end run        



/**
 * <p>
 * Repassa a informacao de que o seu BotaoDeEmergencia foi armado.
 * </p>
 */
    public synchronized void registrarBotaoDeEmergenciaArmado() {        
        controlador.registrarBotaoDeEmergenciaArmado(indElevador);
    } // end registrarEmergenciaArmado        

    
/**
 * <p>
 * Recebe e repassa a informacao de que o BotaoDeEmergencia foi desarmado.
 * </p>
 */
    public synchronized void registrarBotaoDeEmergenciaDesarmado() {        
        controlador.registrarBotaoDeEmergenciaDesarmado(indElevador);
    } // end registrarEmergenciaDesarmado        



/**
 * <p>
 * Informa ao Elevador que o seu BotaoDeEmergencia foi
 * acionado pelo usuario.
 * </p>
 * 
 */
    public void registrarBotaoDeEmergenciaAcionado() {    
    	
    	this.proximoDestino = this.andarCorrente;
 
    	direcao = Controlador.parado;
   
        controlador.registrarPosicaoElevador(indElevador, proximoDestino);
        
        getBotaoDeEmergencia().solicitarAndar();
        
    	int andar;
        andar = 1;
        for (andar = 1; andar <= numAndares; andar++) {
            if (getBotaoDeSelecao(andar).isArmado()) {
            	getBotaoDeSelecao(andar).registrarElevadorNoAndar();
            }
        }

        andar = 1;
        for (andar = 1; andar <= numAndares; andar++) {
            if (controlador.isArmadoBotaoSubida(andar) ) {
            	controlador.botaoSubida.get(andar).registrarElevadorNoAndar();
            } 
        } 
        
        andar = 1;
        for (andar = 1; andar <= numAndares; andar++) {
            if (controlador.isArmadoBotaoDescida(andar) ) {
            	controlador.botaoDescida.get(andar).registrarElevadorNoAndar();
            }
        } 
    } // end registrarBotaoDeEmergenciaoAcionado        



/**
 * <p>
 * Retorna BotaoDeEmergencia.
 * </p>
 * 
 * @return um BotaoDeEmergencia.
 */
    private BotaoDeEmergencia getBotaoDeEmergencia() {        
        return botaoDeEmergencia;
    } // end getBotaoDeSelecao        

	
 } // end Elevador



